Skip to content

support JupyterHub access scopes#863

Merged
consideRatio merged 7 commits intodask:mainfrom
minrk:jupyterhub-auth-scopes
Feb 17, 2026
Merged

support JupyterHub access scopes#863
consideRatio merged 7 commits intodask:mainfrom
minrk:jupyterhub-auth-scopes

Conversation

@minrk
Copy link
Contributor

@minrk minrk commented Feb 3, 2025

Most details can be found in #829 but this would allow standard and more fine-grained control of access to the gateway service for tokens. The default is currently unchanged, for maximum compatibility,. though I would consider it insecure.

To enable this with users being able to access this service by default from their server (minimal change from current default capabilities), a deployment needs to:

  1. configure dask-gateway-server to know its service name via either: c.JupyterHubAuthenticator.jupyterhub_service_name, the environment variable JUPYTERHUB_SERVICE_NAME, or via the Helm chart's auth.jupyterhub.jupyterhubServiceName. Usually to "dask-gateway".
  2. grant users the access:services!service=dask-gateway scope via the default user role (or a different role, if it should be less than all users, which is part of the point)
  3. grant server tokens the same scope via the Spawner.server_token_scopes (or the server role, prior to JupyterHub 4.0)

The main idea of this is it enables both:

  1. not all users have access to the gateway, and/or
  2. not all tokens have access to the gateway

since as it is now, JupyterHubAuth grants any token provided to any jupyterhub service full access to the dask gateway.

TODO:

  • decide on enabling by default
  • docs for change, how to enable
  • update tests
  • update gateway chart? Maybe also daskhub chart?

closes #829

- disabled by default for backward-compatibility
- opt-in by setting jupyterhub_service_name
- prefix service usernames so they don't collide with users
@minrk minrk force-pushed the jupyterhub-auth-scopes branch from 9235074 to 702dc63 Compare February 3, 2025 15:51
@minrk
Copy link
Contributor Author

minrk commented Feb 3, 2025

Also possibly relevant, since I don't know how dask-labextension works: Does the lab extension use the PageConfig.token to make requests directly to the Gateway, or does it make requests only to a server extension, which then uses $JUPYTERHUB_API_TOKEN? If it uses the jupyterlab token, the service access permission would also need to be granted to Spawner.oauth_client_allowed_scopes

@yuvipanda
Copy link
Contributor

Great to see this existing :)

Given the general lack of activity in this project, I would suggest making this non-breaking to reduce potential support burden on the existing limited set of maintainers

@yuvipanda
Copy link
Contributor

Also I believe the labextension only talks to the serverextension so you should be clear there

@jacobtomlinson
Copy link
Member

I would definitely appreciate it being non-breaking. Feel free to ping me directly for review when it's ready.

@minrk minrk changed the title [wip] support JupyterHub access scopes support JupyterHub access scopes Feb 13, 2026
@minrk minrk marked this pull request as ready for review February 13, 2026 18:22
@minrk
Copy link
Contributor Author

minrk commented Feb 13, 2026

The code was already entirely unchanged in defaults, so I stuck with that. There's a warning, because the defaults are insecure.

This is now documented and tested and extended to the dask-gateway chart, all with defaults unchanged.

I'm not sure insecure by default is the best thing, but given the activity level of this repo, it makes sense.

Maybe in daskhub the defaults can be changed to be more secure, since it can tie it all together to still grant all users access.

@minrk
Copy link
Contributor Author

minrk commented Feb 13, 2026

cc @jacobtomlinson for review

Copy link
Collaborator

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful @minrk! Thank you for working this!!

I look for clarity about the one review comment I made, but I figure we can proceed with a merge besides that.

Comment on lines 432 to 437
# avoid collisions between user names and service names
# 'kind' may be 'user' or 'service'
username = data["name"]
if data["kind"] != "user" or username.startswith(("user:", "service:")):
# avoid collision without changing the name for users
username = f"{data['kind']}:{username}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this code have or not rather than or in the if statement? I didn't understand this code block.

The dask-gateway-server user's username will be changed if it starts with user: or service:, which perhaps may or may not ever be allowed though - I'm not sure. It contradicts the comment about not changing the username. Perhaps it should include "except in the edge case the name is starting with "user:" or "service:".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the comment and simplified the condition to clarify.

This is to avoid collisions and also avoid changes for the ~99% of cases where it's just users accessing the service. JupyterHub doesn't put limits on usernames, so e.g. a user can technically have the name service:name (unlikely because most identity providers don't allow this). This ensures that a user is never misrepresented as service:name in this situation, they would be user:service:name.

gateway doesn't really use this for anything, so it's not that important. But this at least guarantees there is no collision. You can parse the result with:

if ":" not in username:
    kind = "user"
    name = username
else:
    kind, _, name = username.partition(":")

and it will always be correct, even for names containing :.

@consideRatio
Copy link
Collaborator

consideRatio commented Feb 16, 2026

(EDIT: flakey, resolved)

The Py 3.14 / go 1.26 test passed twice in main branch, but it failed twice in this PR =/

Maybe rebase on main and see if it changes anything as a first step.

@consideRatio consideRatio force-pushed the jupyterhub-auth-scopes branch from 0be7553 to 2d4a10c Compare February 17, 2026 09:10
@consideRatio consideRatio merged commit 9fc1e48 into dask:main Feb 17, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider JupyterHub authentication rework

4 participants